#include "../config.h"

#include <glibmm/i18n.h>

#include <boost/preprocessor/slot/counter.hpp>
#include <boost/version.hpp>

#ifdef USE_MIGEMO
# include <migemo.h>
#endif

#ifdef HAVE_SVN_H
# include "svn.h"
#endif

#include "application.h"
#include "command.h"
#include "mainwindow.h"
#include "dialog.h"
#include "icon.h"
#include "environment.h"
#include "format.h"

#ifdef HAVE_BUILDINFO_H
# include "buildinfo.h"
#endif

namespace gpyp {

//========================
// UptestDialog::Provider
Gdk::Color UptestDialog::Provider::_color_green("#008000");
Gdk::Color UptestDialog::Provider::_color_red("#800000");

void UptestDialog::Provider::set_status(Status status) {
  if (status != status_) {
    status_ = status;
    signal_status_changed_(status_);
  }
}

void UptestDialog::Provider::set_color(Gtk::Widget &widget, boost::logic::tribool b) {
  Pango::FontDescription fd;
  if (b) {
    widget.modify_fg(Gtk::STATE_NORMAL, _color_green);
    fd.set_weight(Pango::WEIGHT_BOLD);
  } else if (!b) {
    widget.modify_fg(Gtk::STATE_NORMAL, _color_red);
    fd.set_weight(Pango::WEIGHT_BOLD);
  } else {
    widget.modify_fg(Gtk::STATE_NORMAL, widget.get_style()->get_fg(Gtk::STATE_NORMAL));
  }
  widget.modify_font(fd);
}
void UptestDialog::Provider::set_bool(Gtk::Label &label, bool value) {
  label.set_text(tostr(value));
  set_color(label, value);
}

UptestDialog::Provider::Provider(Application &app) :
  app_   (app),
  status_(STATUS_NONE)
{
}
UptestDialog::Provider::~Provider() {
}

//========================
// UptestDialog::YP4G
void UptestDialog::YP4G::on_start_element(const char  */*element_name*/,
                                          const char **attribute_names,
                                          const char **attribute_values,
                                          GError     **/*error*/) {
  if (is_path("/yp4g/yp")) {
    for(const char **name = attribute_names, **value = attribute_values;
        *name && *value; ++name, ++value) {
      if (!strcmp(*name, "name")) {
        data_->yp_name = *value;
      }
    }
  } else if (is_path("/yp4g/host")) {
    for(const char **name = attribute_names, **value = attribute_values;
        *name && *value; ++name, ++value) {
      if (!strcmp(*name, "ip")) {
        data_->host_ip = *value;
      } else if (!strcmp(*name, "port_open")) {
        data_->host_port_open = strcmp(*value, "1") == 0;
      } else if (!strcmp(*name, "speed")) {
        data_->host_speed = toint(*value, -1);
      } else if (!strcmp(*name, "over")) {
        data_->host_over = strcmp(*value, "1") == 0;
      }
    }
  } else if (is_path("/yp4g/uptest")) {
    for(const char **name = attribute_names, **value = attribute_values;
        *name && *value; ++name, ++value) {
      if (!strcmp(*name, "checkable")) {
        data_->uptest_checkable = strcmp(*value, "1") == 0;
      } else if (!strcmp(*name, "remain")) {
        data_->uptest_remain = toint(*value, -1);
      }
    }
  } else if (is_path("/yp4g/uptest_srv")) {
    for(const char **name = attribute_names, **value = attribute_values;
        *name && *value; ++name, ++value) {
      if (!strcmp(*name, "addr")) {
        data_->srv_addr = *value;
      } else if (!strcmp(*name, "port")) {
        data_->srv_port = toint(*value, -1);
      } else if (!strcmp(*name, "object")) {
        data_->srv_object = *value;
      } else if (!strcmp(*name, "post_size")) {
        data_->srv_post_size = toint(*value, -1);
      } else if (!strcmp(*name, "limit")) {
        data_->srv_limit = toint(*value, -1);
      } else if (!strcmp(*name, "interval")) {
        data_->srv_interval = toint(*value, -1);
      } else if (!strcmp(*name, "enabled")) {
        data_->srv_enabled = strcmp(*value, "1") == 0;
      }
    }
  }
}

void UptestDialog::YP4G::loader_on_error(const RefError &err) {
  error(err->get_message());
}
void UptestDialog::YP4G::error(const Glib::ustring &message) {
  log::err << "Uptest-YP4G: " << message << log::endl;
  set_status(STATUS_ERROR);
}

bool UptestDialog::YP4G::is_checkable() {
  return (data_ &&
          data_->host_port_open &&
          !data_->host_over &&
          data_->srv_post_size > 0 &&
          data_->srv_enabled &&
          data_->uptest_checkable);
}
bool UptestDialog::YP4G::remain_timeout() {
  int r = remain_time_ - time(NULL);
  if (r > 0) {
    d_uptest_remain_.set_text(tostr(r));
    set_color(d_uptest_remain_, false);
    return true;
  } else {
    d_uptest_remain_.set_text("0");
    set_color(d_uptest_remain_, true);
    set_status(is_checkable() ? STATUS_READY : STATUS_UNTESTABLE);
    conn_remain_timer_.disconnect();
    return false;
  }
}

void UptestDialog::YP4G::clear() {
  d_yp_name_         .set_text(Glib::ustring());
  d_host_ip_         .set_text(Glib::ustring());
  d_host_port_open_  .set_text(Glib::ustring());
  d_host_speed_      .set_text(Glib::ustring());
  d_host_over_       .set_text(Glib::ustring());
  d_uptest_checkable_.set_text(Glib::ustring());
  d_uptest_remain_   .set_text(Glib::ustring());
  d_srv_addr_        .set_text(Glib::ustring());
  d_srv_port_        .set_text(Glib::ustring());
  d_srv_object_      .set_text(Glib::ustring());
  d_srv_post_size_   .set_text(Glib::ustring());
  d_srv_limit_       .set_text(Glib::ustring());
  d_srv_interval_    .set_text(Glib::ustring());
  d_srv_enabled_     .set_text(Glib::ustring());
}
void UptestDialog::YP4G::on_information_loaded(const RefResponse response) {
  data_.reset(new Data());
  parse(response->get_source());

  d_yp_name_         .set_text(data_->yp_name);
  d_host_ip_         .set_text(data_->host_ip);
  set_bool(d_host_port_open_, data_->host_port_open);
  d_host_speed_      .set_text(tostr(data_->host_speed));
  set_bool(d_host_over_, data_->host_over);
  set_bool(d_uptest_checkable_, data_->uptest_checkable);
  d_uptest_remain_   .set_text(tostr(data_->uptest_remain));
  set_color(d_uptest_remain_, data_->uptest_remain == 0);
  d_srv_addr_        .set_text(data_->srv_addr);
  d_srv_port_        .set_text(tostr(data_->srv_port));
  d_srv_object_      .set_text(data_->srv_object);
  d_srv_post_size_   .set_text(tostr(data_->srv_post_size));
  d_srv_limit_       .set_text(tostr(data_->srv_limit));
  d_srv_interval_    .set_text(tostr(data_->srv_interval));
  set_bool(d_srv_enabled_, data_->srv_enabled);

  log::log << "Uptest-YP4G: " << *get_yp()->name() << log::endl;
  log::dbg << "  /yp4g/yp[name]:              " << data_->yp_name          << log::endl
           << "  /yp4g/host[ip]:              " << data_->host_ip          << log::endl
           << "  /yp4g/host[port_open]:       " << data_->host_port_open   << log::endl
           << "  /yp4g/host[speed]:           " << data_->host_speed       << log::endl
           << "  /yp4g/host[over]:            " << data_->host_over        << log::endl
           << "  /yp4g/uptest[checkable]:     " << data_->uptest_checkable << log::endl
           << "  /yp4g/uptest[remain]:        " << data_->uptest_remain    << log::endl
           << "  /yp4g/uptest_srv[addr]:      " << data_->srv_addr         << log::endl
           << "  /yp4g/uptest_srv[port]:      " << data_->srv_port         << log::endl
           << "  /yp4g/uptest_srv[object]:    " << data_->srv_object       << log::endl
           << "  /yp4g/uptest_srv[post_size]: " << data_->srv_post_size    << log::endl
           << "  /yp4g/uptest_srv[limit]:     " << data_->srv_limit        << log::endl
           << "  /yp4g/uptest_srv[interval]:  " << data_->srv_interval     << log::endl
           << "  /yp4g/uptest_srv[enabled]:   " << data_->srv_enabled      << log::endl;

  if (data_->uptest_remain > 0) {
    remain_time_ = time(NULL) + data_->uptest_remain;
    conn_remain_timer_.disconnect();
    conn_remain_timer_ = Glib::signal_timeout()
      .connect(sigc::mem_fun(*this, &YP4G::remain_timeout), 1000);
    set_status(STATUS_UNTESTABLE);
  } else if (is_checkable()) {
    set_status(STATUS_READY);
  } else {
    set_status(STATUS_UNTESTABLE);
  }
}
void UptestDialog::YP4G::update_information() {
  clear();
  conn_remain_timer_.disconnect();
  data_.reset();
  set_status(STATUS_PREPARING);

  using namespace loader;
  loader_conn_ = Pool::request(url_,
                               sigc::mem_fun(*this, &YP4G::on_information_loaded),
                               sigc::mem_fun(*this, &YP4G::loader_on_error));
  loader_conn_->load();
}

void UptestDialog::YP4G::on_prepare(RefYellowPage yp) {
  try {
    url_ = URL::get_dir(*yp->url()) + "/yp4g.xml";
    update_information();
  } catch(const InvalidURL &ex) {
    error(ex.what());
  }
}
void UptestDialog::YP4G::on_test_complete(const RefResponse /*response*/) {
  log::dbg << "YP4G::on_test_complete" << log::endl;
  update_information();
}
void UptestDialog::YP4G::on_test() {
  if (data_->srv_post_size <= 0) {
    error(_("Invalid data"));
    return;
  }

  Glib::ustring host = d_srv_addr_.get_text();
  Glib::ustring port = d_srv_port_.get_text();
  Glib::ustring path = d_srv_object_.get_text();

  try {
    URL url(Glib::ustring("http://") + host + ":" + port + path);

    size_t post_size = data_->srv_post_size * 1000;
    std::string post_data;
    post_data.reserve(post_size + 1);
    for(size_t i = 0; i < post_size; ++i) {
      post_data += 'a' + (rand() % ('z' - 'a' + 1));
    }

    log::log << "Uptest-YP4G: "
             << url.to_string() << " << " << post_size << log::endl;

    using namespace loader;
    RefHTTPRequest req = HTTPRequest::create(url, post_data);
    req->set_header("Content-Length", tostr(post_size + 5));
    req->set_header("Content-Type", "application/x-www-form-urlencoded");
    loader_conn_ = Pool::request(req,
                                 sigc::mem_fun(*this, &YP4G::on_test_complete),
                                 sigc::mem_fun(*this, &YP4G::loader_on_error));
    loader_conn_->set_timeout(60);
    loader_conn_->load();
    set_status(STATUS_TESTING);
  } catch(const InvalidURL &ex) {
    error(ex.what());
  }
}

UptestDialog::YP4G::YP4G(Application &app) :
  Provider           (app),
  l_yp_name_         ("yp[name]",               Gtk::ALIGN_LEFT),
  l_host_ip_         ("host[ip]",               Gtk::ALIGN_LEFT),
  l_host_port_open_  ("host[port_open]",        Gtk::ALIGN_LEFT),
  l_host_speed_      ("host[speed]",            Gtk::ALIGN_LEFT),
  l_host_over_       ("host[over]",             Gtk::ALIGN_LEFT),
  l_uptest_checkable_("uptest[checkable]",      Gtk::ALIGN_LEFT),
  l_uptest_remain_   ("uptest[remain]",         Gtk::ALIGN_LEFT),
  l_srv_addr_        ("uptest_srv[addr]",       Gtk::ALIGN_LEFT),
  l_srv_port_        ("uptest_srv[port]",       Gtk::ALIGN_LEFT),
  l_srv_object_      ("uptest_srv[object]",     Gtk::ALIGN_LEFT),
  l_srv_post_size_   ("uptest_srv[post_size]",  Gtk::ALIGN_LEFT),
  l_srv_limit_       ("uptest_srv[limit]",      Gtk::ALIGN_LEFT),
  l_srv_interval_    ("uptest_srv[interval]",   Gtk::ALIGN_LEFT),
  l_srv_enabled_     ("uptest_srv[enabled]",    Gtk::ALIGN_LEFT),

  d_yp_name_         (Glib::ustring(), Gtk::ALIGN_LEFT),
  d_host_ip_         (app),
  d_host_port_open_  (Glib::ustring(), Gtk::ALIGN_LEFT),
  d_host_speed_      (Glib::ustring(), Gtk::ALIGN_LEFT),
  d_host_over_       (Glib::ustring(), Gtk::ALIGN_LEFT),
  d_uptest_checkable_(Glib::ustring(), Gtk::ALIGN_LEFT),
  d_uptest_remain_   (Glib::ustring(), Gtk::ALIGN_LEFT),
  d_srv_addr_        (app),
  d_srv_port_        (app),
  d_srv_object_      (app),
  d_srv_post_size_   (Glib::ustring(), Gtk::ALIGN_LEFT),
  d_srv_limit_       (Glib::ustring(), Gtk::ALIGN_LEFT),
  d_srv_interval_    (Glib::ustring(), Gtk::ALIGN_LEFT),
  d_srv_enabled_     (Glib::ustring(), Gtk::ALIGN_LEFT)
{
  d_srv_addr_.set_editable(true);
  d_srv_port_.set_editable(true);
  d_srv_object_.set_editable(true);

  Gtk::AttachOptions ao = Gtk::SHRINK | Gtk::FILL;
  attach(l_yp_name_,          0, 1,  0,  1, ao, ao);
  attach(l_host_ip_,          0, 1,  1,  2, ao, ao);
  attach(l_host_port_open_,   0, 1,  2,  3, ao, ao);
  attach(l_host_speed_,       0, 1,  3,  4, ao, ao);
  attach(l_host_over_,        0, 1,  4,  5, ao, ao);
  attach(l_uptest_checkable_, 0, 1,  5,  6, ao, ao);
  attach(l_uptest_remain_,    0, 1,  6,  7, ao, ao);
  attach(l_srv_addr_,         0, 1,  7,  8, ao, ao);
  attach(l_srv_port_,         0, 1,  8,  9, ao, ao);
  attach(l_srv_object_,       0, 1,  9, 10, ao, ao);
  attach(l_srv_post_size_,    0, 1, 10, 11, ao, ao);
  attach(l_srv_limit_,        0, 1, 11, 12, ao, ao);
  attach(l_srv_interval_,     0, 1, 12, 13, ao, ao);
  attach(l_srv_enabled_,      0, 1, 13, 14, ao, ao);

  attach(d_yp_name_,          1, 2,  0,  1, Gtk::EXPAND | Gtk::FILL, ao);
  attach(d_host_ip_,          1, 2,  1,  2, Gtk::EXPAND | Gtk::FILL, ao);
  attach(d_host_port_open_,   1, 2,  2,  3, Gtk::EXPAND | Gtk::FILL, ao);
  attach(d_host_speed_,       1, 2,  3,  4, Gtk::EXPAND | Gtk::FILL, ao);
  attach(d_host_over_,        1, 2,  4,  5, Gtk::EXPAND | Gtk::FILL, ao);
  attach(d_uptest_checkable_, 1, 2,  5,  6, Gtk::EXPAND | Gtk::FILL, ao);
  attach(d_uptest_remain_,    1, 2,  6,  7, Gtk::EXPAND | Gtk::FILL, ao);
  attach(d_srv_addr_,         1, 2,  7,  8, Gtk::EXPAND | Gtk::FILL, ao);
  attach(d_srv_port_,         1, 2,  8,  9, Gtk::EXPAND | Gtk::FILL, ao);
  attach(d_srv_object_,       1, 2,  9, 10, Gtk::EXPAND | Gtk::FILL, ao);
  attach(d_srv_post_size_,    1, 2, 10, 11, Gtk::EXPAND | Gtk::FILL, ao);
  attach(d_srv_limit_,        1, 2, 11, 12, Gtk::EXPAND | Gtk::FILL, ao);
  attach(d_srv_interval_,     1, 2, 12, 13, Gtk::EXPAND | Gtk::FILL, ao);
  attach(d_srv_enabled_,      1, 2, 13, 14, Gtk::EXPAND | Gtk::FILL, ao);

  set_col_spacings(8);
  set_row_spacings(2);
  show_all();
}
UptestDialog::YP4G::~YP4G() {
  conn_remain_timer_.disconnect();
  if (loader_conn_) loader_conn_->cancel();
}

//========================
// UptestDialog
void UptestDialog::yp_button_on_clicked() {
  app_.commandrunner().webbrowser(*yp_selector_.get_active()->url());
}
void UptestDialog::yp_selector_on_selected(RefYellowPage yp) {
  yp4g_         .hide();
  l_unsupported_.hide();
  l_unselected_ .hide();

  if (yp) {
    Glib::RefPtr<Gdk::Pixbuf> icon;
    if (!(*yp->icon()).empty()) {
      icon = IconManager::load_icon(*yp->icon(), IconManager::ICON_SIZE_16);
    }
    if (!icon) {
      icon = IconManager::load_icon(IconManager::SPECIAL_WEBBROWSER,
                                    IconManager::ICON_SIZE_16);
    }
    yp_icon_.set(icon);
    yp_button_.show();

    switch(*yp->type()) {
    case YPTYPE_YP4G:
      yp4g_.show();
      yp4g_.set_yp(yp);
      break;
    default:
      l_unsupported_.show();
      break;
    }
  } else {
    yp_button_.hide();
    l_unselected_.show();
  }
}

void UptestDialog::yp4g_on_status_changed(Status status) {
  if (!yp4g_.is_visible()) return;

  bool          selectable = false;
  Glib::ustring status_text;
  bool          testable = false;
  switch(status) {
  case STATUS_PREPARING:
    status_text = _("Preparing");
    break;
  case STATUS_UNTESTABLE:
    selectable = true;
    break;
  case STATUS_READY:
    status_text = _("Ready");
    selectable = testable = true;
    break;
  case STATUS_TESTING:
    status_text = _("Testing");
    break;
  case STATUS_ERROR:
    status_text = _("Error");
    selectable = true;
    break;
  default: break;
  }
  yp_selector_.set_sensitive(selectable);
  l_status_.set_text(status_text);
  b_test_->set_sensitive(testable);
}

void UptestDialog::on_response(int response_id) {
  if (response_id == Gtk::RESPONSE_APPLY) {
    if (yp4g_.is_visible()) {
      yp4g_.test();
    }
    return;
  }
  Gtk::Dialog::on_response(response_id);
}

void UptestDialog::set(RefYellowPage yp) {
  yp_selector_.set_active(yp);
}

UptestDialog::UptestDialog(Application &app, Gtk::Window &parent) :
  Gtk::Dialog   (PACKAGE_NAME, parent),
  app_          (app),
  yp4g_         (app_),
  l_unsupported_(_("Unsupported YP type")),
  l_unselected_ (_("No YP selected")),
  b_test_       (NULL)
{
  yp_button_.set_no_show_all(true);
  yp_button_.set_image(yp_icon_);
  yp_button_.signal_clicked()
    .connect(sigc::mem_fun(*this, &UptestDialog::yp_button_on_clicked));

  yp_selector_on_selected(RefYellowPage());
  RefYPCollection ypc = app_.ypcollection();
  for(YPCollection::iterator iter = ypc->begin();
      iter != ypc->end(); ++iter) {
    if (*(*iter)->enable()) yp_selector_.add(*iter, *(*iter)->name());
  }
  yp_selector_.signal_selected()
    .connect(sigc::mem_fun(*this, &UptestDialog::yp_selector_on_selected));

  yp4g_.signal_status_changed()
    .connect(sigc::mem_fun(*this, &UptestDialog::yp4g_on_status_changed));

  yp4g_.hide();
  yp4g_.set_no_show_all(true);
  l_unsupported_.set_no_show_all(true);
  l_unselected_.set_no_show_all(true);

  yp_box_.pack_start(yp_button_,   false, false);
  yp_box_.pack_start(yp_selector_, true,  true);

  Gtk::VBox *vbox = get_vbox();
  vbox->pack_start(yp_box_,        false, false);
  vbox->pack_start(yp4g_,          false, false);
  vbox->pack_start(l_unsupported_, true,  true);
  vbox->pack_start(l_unselected_,  true,  true);

  get_action_area()->pack_start(l_status_);
  b_test_ = add_button(_("Test"), Gtk::RESPONSE_APPLY);
  add_button(Gtk::Stock::CLOSE, Gtk::RESPONSE_CLOSE);
  set_default_response(Gtk::RESPONSE_CLOSE);
  show_all();
}

//========================
// ChPropertyView
void ChPropertyView::prepare_commandopts(CommandOpts &opts) {
  TextView::prepare_commandopts(opts);
  opts.ch = ch_;
}

Gdk::Rectangle *
ChPropertyView::on_show_property(Glib::RefPtr<Gtk::TextBuffer> buf,
                                 RefChannel                    ch,
                                 const Channel::Property      *prop,
                                 Gdk::Rectangle               &rect) {
  if (buf->begin() != buf->end()) {
    buf->insert(buf->end(), "\n");
  }
  const Glib::ustring &val = (ch.get()->*(prop->func_str))();
  if (set_mark_) {
    buf->create_mark(Glib::ustring("name_") + prop->name, buf->end());
    buf->insert(buf->end(), prop->name);
    get_iter_location(buf->end(), rect);
    buf->insert(buf->end(), "\t");
    buf->create_mark(Glib::ustring("value_") + prop->name, buf->end());
    buf->insert(buf->end(), val);
  } else {
    buf->insert(buf->end(), prop->name);
    get_iter_location(buf->end(), rect);
    buf->insert(buf->end(), "\t");
    buf->insert(buf->end(), val);
  }
  return &rect;
}

void ChPropertyView::set(RefChannel ch) {
  ch_ = ch;
  refresh();
}

void ChPropertyView::refresh() {
  Glib::RefPtr<Gtk::TextBuffer> buf = get_buffer();
  buf->set_text(Glib::ustring());

  if (ch_) {
    int right = 0;
    for(const Channel::Property **p = Channel::_properties; *p; ++p) {
      Gdk::Rectangle  rect;
      Gdk::Rectangle *rect_p = on_show_property(buf, ch_, *p, rect);
      if (rect_p && right < rect_p->get_x()) {
        right = rect_p->get_x();
      }
    }

    Pango::TabArray tabs(64);
    tabs.set_tab(0, Pango::TAB_LEFT, right + 16);
    set_tabs(tabs);
  }
}

ChPropertyView::ChPropertyView(Application &app, bool set_mark):
  TextView (app),
  set_mark_(set_mark)
{
  set_editable(false);
}

//========================
// FavoriteMatchView::PropertyView
Gdk::Rectangle *
FavoriteMatchView::PropertyView::
on_show_property(Glib::RefPtr<Gtk::TextBuffer> buf,
                 RefChannel                    ch,
                 const Channel::Property      *prop,
                 Gdk::Rectangle               &rect) {
  if (prop == &Channel::_prop_name ||
      prop == &Channel::_prop_genre ||
      prop == &Channel::_prop_desc ||
      prop == &Channel::_prop_comment ||
      prop == &Channel::_prop_track_artist ||
      prop == &Channel::_prop_track_album ||
      prop == &Channel::_prop_track_title ||
      prop == &Channel::_prop_contact ||
      prop == &Channel::_prop_ypname ||
      prop == &Channel::_prop_type ||
      prop == &Channel::_prop_id) {
    return ChPropertyView::on_show_property(buf, ch, prop, rect);
  }
  return NULL;
}

FavoriteMatchView::PropertyView::PropertyView(Application &app, bool set_mark) :
  ChPropertyView(app, set_mark)
{
}

//========================
// FavoriteMatchView
void FavoriteMatchView::highlight(conf::RefAttributeBool   target,
                                  RefRegex                 re,
                                  const Channel::Property &prop) {
  if (*target) {
    const Glib::ustring &val = (ch_.get()->*(prop.func_str))();
    long start, length;
    if (re->search(val, start, length)) {
      Glib::RefPtr<Gtk::TextBuffer> buf = propview_.get_buffer();
      Glib::ustring mark_name = Glib::ustring("value_") + prop.name;
      Glib::RefPtr<Gtk::TextMark> mark = buf->get_mark(mark_name);
      Gtk::TextIter iter_s = buf->get_iter_at_mark(mark);
      for(; start > 0; --start) ++iter_s;
      Gtk::TextIter iter_e = iter_s;
      for(; length > 0; --length) ++iter_e;
      buf->apply_tag(tag_matched_, iter_s, iter_e);
    }
  }
}
void FavoriteMatchView::highlight(RefRule rule) {
  if (!rule) return;
  RefRegex re = rule->get_re();
  highlight(rule->target_name(),    re, Channel::_prop_name);
  highlight(rule->target_genre(),   re, Channel::_prop_genre);
  highlight(rule->target_desc(),    re, Channel::_prop_desc);
  highlight(rule->target_comment(), re, Channel::_prop_comment);
  highlight(rule->target_track(),   re, Channel::_prop_track_artist);
  highlight(rule->target_track(),   re, Channel::_prop_track_album);
  highlight(rule->target_track(),   re, Channel::_prop_track_title);
  highlight(rule->target_contact(), re, Channel::_prop_contact);
  highlight(rule->target_ypname(),  re, Channel::_prop_ypname);
  highlight(rule->target_type(),    re, Channel::_prop_type);
  highlight(rule->target_id(),      re, Channel::_prop_id);
}

void FavoriteMatchView::selected() {
  RefFavorite favorite;
  RefRule     rule;
  {
    Gtk::TreeIter iter = treeview_.get_selection()->get_selected();
    if (iter) {
      favorite = iter->get_value(model_column_favorite_);
      rule     = iter->get_value(model_column_rule_);
    }
  }

  Glib::RefPtr<Gtk::TextBuffer>   buf       = propview_.get_buffer();
  Glib::RefPtr<Gtk::TextTagTable> tag_table = buf->get_tag_table();

  if (tag_matched_) tag_table->remove(tag_matched_);
  tag_matched_ = buf->create_tag();
  tag_matched_->property_background_gdk() = Gdk::Color("#008000");
  tag_matched_->property_foreground_gdk() = Gdk::Color("white");

  if (favorite) {
    for(Favorite::iterator iter = favorite->begin();
        iter != favorite->end(); ++iter) {
      highlight(*iter);
    }
  } else if (rule) {
    highlight(rule);
  }
}

void FavoriteMatchView::set(RefChannel ch) {
  ch_ = ch;
  model_->clear();

  if (ch) {
    RefFavoriteMatch fm = ch->get_favorite_match();
    for(FavoriteMatch::iterator faviter = fm->begin();
        faviter != fm->end(); ++faviter) {
      RefFavorite   favorite = *faviter;
      Gtk::TreeIter treeiter = model_->append();
      Gtk::TreeRow  row      = *treeiter;
      row[model_column_name_]     = *favorite->name();
      row[model_column_favorite_] = favorite;

      for(Favorite::iterator ruleiter = favorite->begin();
          ruleiter != favorite->end(); ++ruleiter) {
        RefRule      rule = *ruleiter;
        Gtk::TreeRow row  = *model_->append(treeiter->children());
        row[model_column_name_] = *rule->word();
        row[model_column_rule_] = rule;
      }
    }
  }
  propview_.set(ch);
}

FavoriteMatchView::FavoriteMatchView(Application &app) :
  column_name_(_("Name"), renderer_name_),
  propview_   (app, true)
{
  columns_.add(model_column_name_);
  columns_.add(model_column_favorite_);
  columns_.add(model_column_rule_);
  model_ = Gtk::TreeStore::create(columns_);

  column_name_.add_attribute(renderer_name_.property_text(),
                             model_column_name_);
  treeview_.append_column(column_name_);

  treeview_.set_headers_visible(false);
  treeview_.set_model(model_);
  treeview_.get_selection()->signal_changed()
    .connect(sigc::mem_fun(*this, &FavoriteMatchView::selected));
  sw_treeview_.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
  sw_treeview_.add(treeview_);

  sw_propview_.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
  sw_propview_.add(propview_);

  pack1(sw_treeview_, false, true);
  pack2(sw_propview_, true,  true);
  set_position(128);
}

//========================
// ChInfoDialog
void ChInfoDialog::set_time(Gtk::Label                     &label,
                            const boost::posix_time::ptime &time) {
  using namespace boost::posix_time;
  if (time.is_not_a_date_time()) {
    label.set_text(_("None"));
  } else {
    time_duration td = second_clock::local_time() - time;
    label.set_text(tostr(time) + "(" + tostr(td) + ")");
  }
}

void ChInfoDialog::set(RefChannel ch) {
  if (ch) {
    set_title(ch->get_name());

    d_yp_.set_label(ch->get_ypname());
    set_time(d_created_,     ch->get_time_created());
    set_time(d_last_played_, ch->get_time_last_played());
    set_time(d_ended_,       ch->get_time_ended());

    uint flags = ch->get_flags();
    Glib::ustring flags_str;
#define ADD_FLAG(FLAG_NAME) {                          \
      if (flags & Channel::FLAG_##FLAG_NAME) {         \
        if (!flags_str.empty()) flags_str += ", ";     \
        flags_str += #FLAG_NAME;                       \
      }                                                \
    }
    ADD_FLAG(NEW);
    ADD_FLAG(CHANGED);
    ADD_FLAG(RETURNED);
    ADD_FLAG(NOTIFIED);
    ADD_FLAG(AUTO_PLAYED);
    ADD_FLAG(ENDED);
#undef ADD_FLAG
    d_flags_.set_label(flags_str);
  } else {
    Glib::ustring s;
    set_title(s);
    d_yp_         .set_label(s);
    d_created_    .set_label(s);
    d_last_played_.set_label(s);
    d_ended_      .set_label(s);
    d_flags_      .set_label(s);
  }

  favmatch_.set(ch);
  propview_.set(ch);
}

ChInfoDialog::ChInfoDialog(Application &app, Gtk::Window &parent) :
  Gtk::Dialog   (_("Channel information"), parent),
  app_          (app),
  l_yp_         (_("YellowPage"),  Gtk::ALIGN_LEFT),
  d_yp_         (Glib::ustring(),  Gtk::ALIGN_LEFT),
  l_created_    (_("Created"),     Gtk::ALIGN_LEFT),
  d_created_    (Glib::ustring(),  Gtk::ALIGN_LEFT),
  l_last_played_(_("Last played"), Gtk::ALIGN_LEFT),
  d_last_played_(Glib::ustring(),  Gtk::ALIGN_LEFT),
  l_ended_      (_("Ended"),       Gtk::ALIGN_LEFT),
  d_ended_      (Glib::ustring(),  Gtk::ALIGN_LEFT),
  l_flags_      (_("Flags"),       Gtk::ALIGN_LEFT),
  d_flags_      (Glib::ustring(),  Gtk::ALIGN_LEFT),
  favmatch_     (app_),
  propview_     (app_)
{
#define TABLE_ATTACH(NAME, I)                                           \
  table_info_.attach(l_##NAME##_, 0, 1, I, (I) + 1, Gtk::FILL, Gtk::FILL); \
  table_info_.attach(d_##NAME##_, 1, 2, I, (I) + 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL);

  TABLE_ATTACH(yp,          0);
  TABLE_ATTACH(created,     1);
  TABLE_ATTACH(last_played, 2);
  TABLE_ATTACH(ended,       3);
  TABLE_ATTACH(flags,       4);

#undef TABLE_ATTACH

  table_info_.set_col_spacings(8);
  table_info_.set_row_spacings(2);
  table_info_.set_border_width(2);

  favmatch_.set_border_width(2);

  sw_propview_.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
  sw_propview_.set_border_width(2);
  sw_propview_.add(propview_);

  conn_channel_selected_ = app_.signal_channel_selected()
    .connect(sigc::mem_fun(*this, &ChInfoDialog::set));

  notebook_.append_page(table_info_,  _("Information"));
  notebook_.append_page(favmatch_,    _("Favorite"));
  notebook_.append_page(sw_propview_, _("Properties"));

  get_vbox()->pack_start(notebook_);
  get_vbox()->show_all();
  add_button(Gtk::Stock::CLOSE, Gtk::RESPONSE_CLOSE);
  set_default_size(640, 480);
}
ChInfoDialog::~ChInfoDialog() {
  conn_channel_selected_.disconnect();
}

//========================
// YPInfoDialog::YPTextView
void YPInfoDialog::YPTextView::prepare_commandopts(CommandOpts &opts) {
  TextView::prepare_commandopts(opts);
  opts.yp = dialog_.yp_;
}

YPInfoDialog::YPTextView::YPTextView(Application  &app,
                                     YPInfoDialog &dialog) :
  TextView(app),
  dialog_ (dialog)
{
}

//========================
// YPInfoDialog
void YPInfoDialog::on_response(int response_id) {
  if (response_id == Gtk::RESPONSE_APPLY) {
    set(yp_);
    return;
  }
  Gtk::Dialog::on_response(response_id);
}

void YPInfoDialog::setup_textview(Gtk::TextView &tv,
                                  Gtk::ScrolledWindow &sw) {
  tv.set_editable(false);
  sw.add(tv);
  sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
  sw.set_shadow_type(Gtk::SHADOW_IN);
  sw.set_border_width(2);
}

void YPInfoDialog::set(RefYellowPage yp) {
  yp_ = yp;
  icon_pixbuf_ = IconManager::load_icon(*yp_->icon(),
                                        IconManager::ICON_SIZE_16);

  set_icon(icon_pixbuf_);
  set_title(*yp_->name());

  icon_.set(icon_pixbuf_);
  name_.set_text(*yp_->name());
  url_ .set_text(*yp_->url());

  d_chcount_.set_text(tostr(yp_->get_chcount()));
  d_chcount_valid_.set_text(tostr(yp_->get_chcount_valid()));
  if (yp_->get_last_loaded_time().is_not_a_date_time()) {
    d_last_loaded_.set_text(_("None"));
  } else {
    using namespace boost::posix_time;
    const ptime &time = yp_->get_last_loaded_time();
    time_duration td = second_clock::local_time() - time;
    d_last_loaded_.set_text(tostr(time) + "(" + tostr(td) + ")");
  }

  d_data_size_.set_text("-");
  request_.get_buffer()->set_text(Glib::ustring());
  response_.get_buffer()->set_text(Glib::ustring());
  sw_request_ .hide();
  sw_response_.hide();
  sw_source_  .hide();

  using namespace loader;
  RefConnection conn = yp_->get_connection();
  while(conn) {
    RefHTTPConnection http =
      boost::dynamic_pointer_cast<HTTPConnection>(conn);
    if (http) {
      Glib::RefPtr<Gtk::TextBuffer> req = request_.get_buffer();
      req->insert(req->end(), http->get_request()->to_string("\n"));
      req->insert(req->end(), "\n\n");

      Glib::RefPtr<Gtk::TextBuffer> res = response_.get_buffer();
      res->insert(res->end(), http->get_response()->to_string("\n"));
      res->insert(res->end(), "\n\n");

      sw_request_ .show();
      sw_response_.show();

      const conf::Source &s = http->get_response()->get_source();
      if (s) {
        d_data_size_.set_text(tostr(s.size()));
        source_.get_buffer()->set_text(s.data(), s.data() + s.size());
        sw_source_.show();
      }
    }
    conn = conn->get_next();
  }
}

YPInfoDialog::YPInfoDialog(Application &app, Gtk::Window &parent) :
  Gtk::Dialog     (_("YP information"), parent),
  app_            (app),
  name_           (app),
  url_            (app),
  l_chcount_      (_("Channel count"),       Gtk::ALIGN_LEFT),
  d_chcount_      (Glib::ustring(),          Gtk::ALIGN_LEFT),
  l_chcount_valid_(_("Valid channel count"), Gtk::ALIGN_LEFT),
  d_chcount_valid_(Glib::ustring(),          Gtk::ALIGN_LEFT),
  l_last_loaded_  (_("Last loaded"),         Gtk::ALIGN_LEFT),
  d_last_loaded_  (Glib::ustring(),          Gtk::ALIGN_LEFT),
  l_data_size_    (_("Data size"),           Gtk::ALIGN_LEFT),
  d_data_size_    (Glib::ustring(),          Gtk::ALIGN_LEFT),
  request_        (app, *this),
  response_       (app, *this),
  source_         (app, *this)
{
  name_box_.pack_start(icon_, false, true);
  name_box_.pack_start(name_);

#define TABLE_ATTACH(NAME, I)                                           \
  table_info_.attach(l_##NAME##_, 0, 1, I, (I) + 1, Gtk::FILL, Gtk::FILL); \
  table_info_.attach(d_##NAME##_, 1, 2, I, (I) + 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL);

  TABLE_ATTACH(chcount,       0);
  TABLE_ATTACH(chcount_valid, 1);
  TABLE_ATTACH(last_loaded,   2);
  TABLE_ATTACH(data_size,     3);

#undef TABLE_ATTACH

  table_info_.set_col_spacings(8);
  table_info_.set_row_spacings(2);
  table_info_.set_border_width(2);

  setup_textview(request_,  sw_request_);
  setup_textview(response_, sw_response_);
  setup_textview(source_,   sw_source_);

  notebook_.append_page(table_info_,  _("Information"));
  notebook_.append_page(sw_request_,  _("Request"));
  notebook_.append_page(sw_response_, _("Response"));
  notebook_.append_page(sw_source_,   _("Source"));

  Gtk::VBox *vbox = get_vbox();
  vbox->pack_start(name_box_, false, true);
  vbox->pack_start(url_, false, true);
  vbox->pack_start(notebook_);
  vbox->show_all();
  add_button(Gtk::Stock::REFRESH, Gtk::RESPONSE_APPLY);
  add_button(Gtk::Stock::CLOSE,   Gtk::RESPONSE_CLOSE);
  set_default_response(Gtk::RESPONSE_CLOSE);

  set_default_size(480, 320);
}

//========================
// ConfigView
void ConfigView::create_tree(Gtk::TreeIter    iter,
                             conf::RefElement element) {
  conf::RefDocument doc =
    boost::dynamic_pointer_cast<conf::Document>(element);
  Gtk::TreeRow row = *iter;
  row[model_column_node_] = element;
  if (doc) {
    row[model_column_name_] = doc->get_file_name();
  } else {
    row[model_column_name_] = element->get_name();
  }
  row[model_column_visible_] = true;

  typedef conf::Element::AttributeList AttributeList;
  const AttributeList &attributes = element->get_attributes();
  for(AttributeList::const_iterator aiter = attributes.begin();
      aiter != attributes.end(); ++aiter) {
    Gtk::TreeRow           row  = *model_->append(iter->children());
    conf::RefAttributeBase attr = *aiter;
    row[model_column_node_]    = attr;
    row[model_column_name_]    = attr->get_name();
    row[model_column_visible_] = true;
  }

  typedef conf::Element::ElementList ElementList;
  const ElementList &elements = element->get_elements();
  for(ElementList::const_iterator eiter = elements.begin();
      eiter != elements.end(); ++eiter) {
    create_tree(model_->append(iter->children()), *eiter);
  }

  conf::RefCollectionBase coll =
    boost::dynamic_pointer_cast<conf::CollectionBase>(element);
  if (coll) {
    ElementList items = coll->get_items();
    for(ElementList::iterator iiter = items.begin();
        iiter != items.end(); ++iiter) {
      create_tree(model_->append(iter->children()), *iiter);
    }
  }
}

bool ConfigView::set_visible(Gtk::TreeIter iter, RefRegex re) {
  const Gtk::TreeNodeChildren &children = iter->children();
  bool visible = false;
  if (children.size()) {
    for(Gtk::TreeNodeChildren::const_iterator iter = children.begin();
        iter != children.end(); ++iter) {
      if (set_visible(iter, re)) visible = true;
    }
  }
  if (!iter) return visible;

  if (!visible) {
    if (!(visible = !re || re->match(iter->get_value(model_column_name_)))) {
      conf::RefAttributeBase attr = boost::dynamic_pointer_cast<conf::AttributeBase>(iter->get_value(model_column_node_));
      if (attr) visible = re->match(attr->get_string());
    }
  }
  iter->set_value(model_column_visible_, visible);
  return visible;
}

void ConfigView::renderer_name_on_cell_data(Gtk::CellRenderer   *cell,
                                            const Gtk::TreeIter &iter) {
  conf::RefNode node = iter->get_value(model_column_node_);
  Gtk::CellRendererText *renderer =
    dynamic_cast<Gtk::CellRendererText *>(cell);

  renderer->property_weight() = (node->get_dirty()
                                 ? Pango::WEIGHT_BOLD
                                 : Pango::WEIGHT_NORMAL);
}
void ConfigView::renderer_value_on_cell_data(Gtk::CellRenderer   *cell,
                                             const Gtk::TreeIter &iter) {
  conf::RefNode          node = iter->get_value(model_column_node_);
  conf::RefAttributeBase attr =
    boost::dynamic_pointer_cast<conf::AttributeBase>(node);
  Gtk::CellRendererText *renderer =
    dynamic_cast<Gtk::CellRendererText *>(cell);

  renderer->property_editable() = attr;
  renderer->property_weight()   = (node->get_dirty()
                                   ? Pango::WEIGHT_BOLD
                                   : Pango::WEIGHT_NORMAL);
  if (attr) {
    renderer->property_text() = attr->get_string();
  } else {
    renderer->property_text() = Glib::ustring();
  }
}
void ConfigView::renderer_value_on_edited(const Glib::ustring &path,
                                          const Glib::ustring &new_text) {
  Gtk::TreeIter          iter = model_->get_iter(path);
  conf::RefNode          node = iter->get_value(model_column_node_);
  conf::RefAttributeBase attr =
    boost::dynamic_pointer_cast<conf::AttributeBase>(node);
  if (attr) {
    try {
      attr->set_string(new_text);
    } catch(const std::exception &ex) {
      ErrorDialog d(_("Invalid value"), ex.what());
      d.run();
    }
  }
}

bool ConfigView::on_key_press_event(GdkEventKey *event) {
  if (event->type == GDK_KEY_PRESS &&
      event->state & GDK_CONTROL_MASK &&
      event->keyval == GDK_f) {
    search_box_.show();
    search_box_.grab_focus();
    return true;
  }
  return Gtk::VBox::on_key_press_event(event);
}
void ConfigView::search_on_changed() {
  RefRegex re = search_->get();
  view_.unset_model();
  set_visible(model_->children(), re);
  view_.set_model(filter_);
  view_.set_search_column(-1);
  if (re) view_.expand_all();
}
void ConfigView::search_box_on_hide() {
  view_.grab_focus();
}

ConfigView::ConfigView(Application &app) :
  column_name_ (_("Name"),  renderer_name_),
  column_value_(_("Value"), renderer_value_),
  search_      (app.conf().config_view()->search()),
  search_box_  (app, search_, app.conf().input()->search())
{
  model_columns_.add(model_column_node_);
  model_columns_.add(model_column_name_);
  model_columns_.add(model_column_visible_);

  model_ = Gtk::TreeStore::create(model_columns_);
  create_tree(model_->append(), app.conf().main());
  create_tree(model_->append(), app.conf().menu());
  create_tree(model_->append(), app.conf().favorite());
  create_tree(model_->append(), app.conf().input());

  column_name_.add_attribute(renderer_name_.property_text(),
                             model_column_name_);
  column_name_.set_cell_data_func(renderer_name_, sigc::mem_fun(*this, &ConfigView::renderer_name_on_cell_data));

  renderer_value_.signal_edited()
    .connect(sigc::mem_fun(*this, &ConfigView::renderer_value_on_edited));
  column_value_.set_cell_data_func(renderer_value_, sigc::mem_fun(*this, &ConfigView::renderer_value_on_cell_data));

  view_.append_column(column_name_);
  view_.append_column(column_value_);

  filter_ = Gtk::TreeModelFilter::create(model_);
  filter_->set_visible_column(model_column_visible_);
  view_.set_model(filter_);
  view_.set_search_column(-1);
  sw_view_.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
  sw_view_.add(view_);

  search_->signal_changed()
    .connect(sigc::mem_fun(*this, &ConfigView::search_on_changed));
  search_box_.signal_hide()
    .connect(sigc::mem_fun(*this, &ConfigView::search_box_on_hide));
  search_box_.set_no_show_all(true);
  pack_start(search_box_, false, false);
  pack_start(sw_view_,    true, true);

  add_events(Gdk::KEY_PRESS_MASK);
  set_spacing(2);
}

//========================
// ConfigViewDialog
ConfigViewDialog::ConfigViewDialog(Application &app, Gtk::Window &parent) :
  Gtk::Dialog(_("Config viewer"), parent),
  view_      (app)
{
  get_vbox()->pack_start(view_);
  add_button(Gtk::Stock::CLOSE, Gtk::RESPONSE_CLOSE);
  resize(640, 480);
  show_all();
}

//========================
// HelpBCDialog
void HelpBCDialog::enum_commands(const Glib::ustring &comm, BuiltinCommand &bc, int *right) {
  const Glib::ustring &hs = bc.get_help_string();

  Glib::RefPtr<Gtk::TextBuffer> buf = view_.get_buffer();
  buf->insert(buf->end(), "@{" + comm + "}");

  Gdk::Rectangle rect;
  view_.get_iter_location(buf->end(), rect);
  if (*right < rect.get_x()) *right = rect.get_x();
  if (!hs.empty()) {
    buf->insert(buf->end(), "\t");
    buf->insert(buf->end(), hs);
  }
  buf->insert(buf->end(), "\n");
}

HelpBCDialog::HelpBCDialog(Application &app, Gtk::Window &parent) :
  Gtk::Dialog("Builtin commands", parent),
  view_      (app, false, Gtk::WRAP_NONE)
{
  int right = 0;
  app.bc().enum_commands(sigc::bind(sigc::mem_fun(*this, &HelpBCDialog::enum_commands), &right));

  Pango::TabArray tabs(64);
  tabs.set_tab(0, Pango::TAB_LEFT, right + 16);
  view_.set_tabs(tabs);
  view_.set_editable(false);

  sw_view_.add(view_);
  sw_view_.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);

  get_vbox()->pack_start(sw_view_);
  add_button(Gtk::Stock::CLOSE, Gtk::RESPONSE_CLOSE);
  resize(480, 320);
  show_all();
}

//========================
// HelpOptionDialog
HelpOptionDialog::HelpOptionDialog(Application &app, Gtk::Window &parent) :
  Gtk::Dialog(_("Commandline options"), parent),
  view_      (app, true, Gtk::WRAP_NONE)
{
#if GLIB_CHECK_VERSION(2, 14, 0)
  view_.get_buffer()->set_text(app.opts().get_help());
#endif
  view_.set_editable(false);

  get_vbox()->pack_start(view_);
  add_button(Gtk::Stock::CLOSE, Gtk::RESPONSE_CLOSE);
  show_all();
}

//========================
// EnvInfoDialog
EnvInfoDialog::InfoTable EnvInfoDialog::_info_tables[] = {
  {"Version",      &EnvInfoDialog::i_version},
#ifdef SVNVERSION
  {"SVN",          &EnvInfoDialog::i_svnversion},
#endif
  {"OS",            env::get_os},
  {"Distribution",  env::get_distribution},
  {"DE",            env::get_de},
#ifdef AC_CONFIGURE_ARGS
  {"configure",    &EnvInfoDialog::i_configure},
#endif
#ifdef BUILD_TIME
  {"Built time",   &EnvInfoDialog::i_build_time},
#endif
  {"Gtk",          &EnvInfoDialog::i_gtk},
  {"boost",        &EnvInfoDialog::i_boost},
#if (REGEX_MODULE == REGEX_MODULE_ONIGURUMA)
  {"oniguruma",    &EnvInfoDialog::i_oniguruma},
#endif
#ifdef USE_MIGEMO
  {"Migemo",       &EnvInfoDialog::i_migemo},
#endif
  {NULL,            NULL}
};

Glib::ustring EnvInfoDialog::i_version() {
  return PACKAGE_VERSION;
}
#ifdef SVNVERSION
Glib::ustring EnvInfoDialog::i_svnversion() {
  return "rev"SVNVERSION;
}
#endif
#ifdef AC_CONFIGURE_ARGS
Glib::ustring EnvInfoDialog::i_configure() {
  return AC_CONFIGURE_ARGS;
}
#endif
#ifdef BUILD_TIME
Glib::ustring EnvInfoDialog::i_build_time() {
  time_t time = BUILD_TIME;
  struct tm time_tm;
  if (localtime_r(&time, &time_tm)) {
    char buf[64];
    if (strftime(buf, sizeof(buf), "%Y/%m/%d %H:%M:%S", &time_tm) > 0) {
      return buf;
    }
  }
  return Glib::ustring();
}
#endif
Glib::ustring EnvInfoDialog::i_gtk() {
  std::stringstream ss;
  ss << "gtkmm"  << env::get_gtkmm_version() << "/"
     << "glibmm" << env::get_glibmm_version();
  return ss.str();
}
Glib::ustring EnvInfoDialog::i_boost() {
  return BOOST_LIB_VERSION;
}
#if (REGEX_MODULE == REGEX_MODULE_ONIGURUMA)
Glib::ustring EnvInfoDialog::i_oniguruma() {
  return onig_version();
}
#endif
#ifdef USE_MIGEMO
Glib::ustring EnvInfoDialog::i_migemo() {
  return MIGEMO_VERSION;
}
#endif

EnvInfoDialog::EnvInfoDialog(Application &app, Gtk::Window &parent) :
  Gtk::Dialog(_("Environment information"), parent),
  view_      (app, false, Gtk::WRAP_NONE)
{
  Glib::RefPtr<Gtk::TextBuffer> buf = view_.get_buffer();
  int right = 0;
  for(InfoTable *t = _info_tables; t->name_; ++t) {
    buf->insert(buf->end(), t->name_);

    Gdk::Rectangle rect;
    view_.get_iter_location(buf->end(), rect);
    if (right < rect.get_x()) right = rect.get_x();

    Glib::ustring data = t->func_();
    if (data.empty()) data = _("Unknown");
    buf->insert(buf->end(), "\t");
    buf->insert(buf->end(), data);
    buf->insert(buf->end(), "\n");
  }

  Pango::TabArray tabs(64);
  tabs.set_tab(0, Pango::TAB_LEFT, right + 16);
  view_.set_tabs(tabs);

  view_.set_editable(false);
  view_.set_wrap_mode(Gtk::WRAP_WORD_CHAR);

  get_vbox()->pack_start(view_);
  add_button(Gtk::Stock::CLOSE, Gtk::RESPONSE_CLOSE);
  resize(480, 320);
  show_all();
}

//========================
// AboutDialog
Application *AboutDialog::_app = NULL;

void AboutDialog::install_url_hook(Application &app) {
  if (_app == NULL) {
    _app = &app;
    set_url_hook(sigc::ptr_fun(AboutDialog::url_hook));
  }
}
void AboutDialog::url_hook(Gtk::AboutDialog    &/*dialog*/,
                           const Glib::ustring &uri) {
  _app->commandrunner().webbrowser(uri);
}

AboutDialog::AboutDialog(Application &app, Gtk::Window &parent) :
  app_(app)
{
  install_url_hook(app);

  static const char *authors[] = {"wowo", NULL};
  static const char *website = "http://gpyp3.googlecode.com/";
  set_authors(Glib::StringArrayHandle(authors, Glib::OWNERSHIP_NONE));
  set_logo_icon_name(IconManager::GPYP);
  set_name(PACKAGE_NAME);
#ifdef SVNVERSION
  set_version(PACKAGE_VERSION"(rev"SVNVERSION")");
#else
  set_version(PACKAGE_VERSION);
#endif
  set_website(website);
  set_website_label(website);
  set_transient_for(parent);
}

}/*gpyp*/
